#include "buffer.h"
#include "mem_alloc.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

struct buffer
{
	size_t head;
	size_t tail;
	size_t max_size;
	size_t cur_size;
	char * msg;
};

#define MIN(x, y) ((x) > (y) ? (y) : (x))
#define MAX(x, y) ((x) > (y) ? (x) : (y))

buffer * buffer_create(int max_size)
{
	buffer * buf;
	if (max_size <= 0) {
		max_size = 0x7FFFFFFF;
	}

	buf = GET_OBJECT(buffer);
	if (buf == 0) {
		return 0;
	}

	buf->head = buf->tail = 0;
	buf->max_size = max_size;
	buf->cur_size = 0;
	buf->msg = 0;
	return buf;
}

void buffer_destory(buffer * buf)
{
	if (buf) {
		if (buf->msg) {
			release_mem(buf->msg, buf->cur_size);
		}
		RELEASE_OBJECT(buffer, buf);
	}
}

static int buffer_grow(buffer * buf, size_t new_size)
{
	int double_size;
	char * msg;
	int old_size;
	int old_start_pos;
	int msg_len;

	if (new_size <= buf->cur_size) {
		return 0;
	}

	if (buf->cur_size >= buf->max_size) {
		return -1;
	}

	// get grow size
	double_size = buf->cur_size * 2;
	new_size = MIN(buf->max_size, MAX(new_size, double_size));

	// save param
	msg = buf->msg;
	old_size = buf->cur_size;
	old_start_pos = buf->head;
	msg_len = buf->tail - buf->head;

	// new memory
	buf->msg = get_mem(new_size);
	if (buf->msg == 0) {
		buf->msg = msg;
		return -1;
	}

	// copy
	if (msg && msg_len > 0) {
		memmove(buf->msg, msg + old_start_pos, msg_len);
	}

	// update param
	buf->cur_size = new_size;
	buf->head = 0;
	buf->tail = msg_len;
	if (msg) release_mem(msg, old_size);
	return 0;
}

static void buffer_merge(buffer * buf)
{
	int msg_len = buf->tail - buf->head;
	if (buf->head != 0) {
		if (msg_len > 0) {
			memmove(buf->msg, buf->msg + buf->head, msg_len);
			buf->head = 0;
			buf->tail = msg_len;
		} else {
			buf->head = buf->tail = 0;
		}
	}
}

static int buffer_add_size(buffer * buf, size_t len)
{
	if(buffer_grow(buf, buffer_read_length(buf) + len) == -1) {
		return -1;
	}

	if (buf->tail + len > buf->cur_size) {
		buffer_merge(buf);
	} 
	return 0;
}

void * buffer_read_ptr(buffer * buf)
{
	return buf->msg + buf->head;
}

size_t buffer_read_length(buffer * buf)
{
	return buf->tail - buf->head;
}

void * buffer_write_ptr(buffer * buf, size_t min_len)
{
	if (((buf->cur_size - buf->tail) < min_len) &&
			(buffer_add_size(buf, min_len) != 0)) {
		return 0;
	}
	return buf->msg + buf->tail;
}

size_t buffer_write_length(buffer * buf)
{
	return buf->cur_size - buf->tail;
}

int buffer_write(buffer * buf, const void * bin, size_t len)
{
	void * p ;
	if (len < 0) {
		return -1;
	}

	p  = buffer_write_ptr(buf, len);
	if (p == 0) {
		return -1;
	}

	if (len > 0) {
		if (bin != 0 ) {
			memmove(buf->msg + buf->tail, bin, len);
		}
		buf->tail += len;
	}
	return 0;
}

int buffer_printf(buffer * buf, const char * fmt, ...)
{
	int usable;

	va_list args;
	int n;
	void * p;

	if (buf == 0 || fmt == 0)  {
		return -1;
	}

	usable = buf->cur_size - buf->tail;

	va_start(args, fmt);
	n = vsnprintf(buf->msg + buf->tail, usable, fmt, args);
	va_end(args);

	if (n > -1 && n < usable) {
		buf->tail += n;
		return 0;
	}

	p = buffer_write_ptr(buf, n + 1);
	if (p == 0) {
		return -1;
	}

	va_start(args, fmt);
	n = vsnprintf(p, buf->cur_size - buf->tail, fmt, args);
	va_end(args);

	buf->tail += n;
	return n;
}

int buffer_read(buffer * buf, void * bin, size_t len)
{
	int left;
	char * p;
	if (len < 0 ) {
		return -1;
	}

	left = buffer_read_length(buf);
	len = MIN(left,len);

	p = buffer_read_ptr(buf);

	if (bin && len > 0) {
		memcpy(bin, p, len);
	}
	buf->head += len;
	return 0;
}

int buffer_scanf(buffer * buf, const char * fmt, ...)
{
	return -1;
}
#define EXPORT_BUFFER_TYPE(T) \
	int buffer_write_##T(buffer * buf, T t) {  \
		return buffer_write(buf, &t, sizeof(T)); \
	} \
int buffer_read_##T(buffer * buf, T * t) {  \
	if (buffer_read_length(buf) < sizeof(T)) return -1; \
	return buffer_read(buf, t, sizeof(T)); \
}


EXPORT_BUFFER_TYPE(char);
EXPORT_BUFFER_TYPE(short);
EXPORT_BUFFER_TYPE(int);

#undef EXPORT_BUFFER_TYPE
